﻿using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using LeanEngine.Entity;
using LeanEngine.Extensions;

namespace LeanEngine.Collections
{
    /// <summary>
    /// Represents a strongly typed list of objects that can be accessed by index. 
    /// Provides an automatic mechanism for tracking changes in the collection.
    /// </summary>
    /// <typeparam name="T">The type of the elements in the list.</typeparam>
    [Serializable]
    public sealed class ChangeTrackerCollection<T> : Collection<T>, IChangeTrack
        where T : class
    {
        #region Declaration Members

        private List<T> deletedItemsList;
        private List<T> insertedItemsList;

        #endregion

        #region Constructor Members

        /// <summary>
        /// Initializes a new instance of the Core.Collections.ChangeTrackerCollection class that is empty and has the default initial capacity.
        /// </summary>
        public ChangeTrackerCollection()
        {
            deletedItemsList = new List<T>();
            insertedItemsList = new List<T>();
        }

        /// <summary>
        /// Initializes a new instance of the Core.Collections.ChangeTrackerCollection class that contains elements copied from the specified
        /// collection and has sufficient capacity to accommodate the number of elements copied.
        /// </summary>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <param name="collection">The collection whose the elements are copied to the new list.</param>
        public ChangeTrackerCollection(IList<T> collection) : base(collection)
        {
            deletedItemsList = new List<T>();
            insertedItemsList = new List<T>();
        }

        #endregion

        #region PropertyInfo Members

        /// <summary>
        /// Gets the System.Collection.Generic.List of elements deleted from the Core.Collections.ChangeTrackerCollection
        /// </summary>
        public IEnumerable<T> DeletedList
        {
            get { return deletedItemsList; }
        }

        /// <summary>
        /// Gets the System.Collection.Generic.List of elements added into the Core.Collections.ChangeTrackerCollection
        /// </summary>
        public IEnumerable<T> InsertedList
        {
            get { return insertedItemsList; }
        }

        #endregion

        #region Method Members

        /// <summary>
        /// Adds a collection of objects to the end of the Core.Collections.ChangeTrackerCollection.
        /// </summary>
        /// <exception cref="ArgumentNullException">Items can not be null.</exception>
        /// <param name="items">
        /// The list of objects to be added to the end of the Core.Collections.ChangeTranckerList.
        /// The value can be null for reference types.
        /// </param>
        public void AddRange(IEnumerable<T> items)
        {
            if (items == null)
            {
                throw new ArgumentNullException("items");
            }

            this.AddRange(items);
        }

        #endregion

        #region ICollection<T> Members

        /// <summary>
        /// Inserts an item to the Core.Collections.ChangeTrackList at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index to be inserted.</param>
        /// <param name="item">The <typeparamref name="T"/> to insert to the Core.Collections.ChangeTrackList.</param>
        protected override void InsertItem(int index, T item)
        {
            var indexof = insertedItemsList.IndexOf(item);
            if (indexof.IsGreaterThan(0))
            {
                insertedItemsList[indexof] = item;
            }
            else
            {
                insertedItemsList.Add(item);
            }

            base.InsertItem(index, item);
        }

        /// <summary>
        /// Replaces the element at the specified index.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="index">The zero-based index of the element to replace.</param>
        /// <param name="item">The new value for the element at the specified index. The value can be null for reference types.</param>
        protected override void SetItem(int index, T item)
        {
            var indexof = insertedItemsList.IndexOf(item);
            if (indexof.IsGreaterThan(0))
            {
                insertedItemsList[indexof] = item;
            }
            else
            {
                insertedItemsList.Add(item);
            }

            base.SetItem(index, item);
        }

        /// <summary>
        /// Removes the element at the specified index.
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        /// <param name="index">The zero-based index of the element to remove.</param>
        protected override void RemoveItem(int index)
        {
            deletedItemsList.Add(this[index]);
            base.RemoveItem(index);
        }

        /// <summary>
        /// Remove all elements from the Core.Collections.ChangeTrackList.
        /// </summary>
        protected override void ClearItems()
        {
            deletedItemsList.AddRange(base.Items);
            base.ClearItems();
        }

        #endregion

        #region IChangeTrack Members

        /// <summary>
        /// Sets the current state of the collection as its initial state.
        /// </summary>
        public void SaveState()
        {
            insertedItemsList.Clear();
            deletedItemsList.Clear();
        }

        public bool HasChanges
        {
            get { return insertedItemsList.Count > 0 || deletedItemsList.Count > 0; }
        }

        /// <summary>
        /// Always return 1.
        /// </summary>
        public int DataVersion
        {
            get { return 1; }
            set { }
        }

        #endregion
    }
}
